﻿using System;
using System.IO.Ports;
using System.Linq;
using CommStudio.Connections;
using System.Threading;

namespace MIC_LOGIC
{
    /// <summary>
    /// Implementation of ILogicFacade
    /// </summary>
    public class LogicFacade : ILogicFacade
    {
        #region events
        public event StringDelegate OnInputDataStreamChanged;
        public event IntegerDelegate OnChecksumErrorChanged;
        public event StringDelegate OnErrorMessage;
        public event StringDelegate OnInfoMessage;
        #endregion

        #region parameters
        private string _portName = "COM2";
        private int _baudRate = 9600;
        private int _dataBits = 8;
        #endregion


        #region fields
        private SerialConnection _serialConn;

        private int _checksum_errors;
        private LogStream _logger;



        //For simultation
        private System.Timers.Timer _timer;
        private Random _rand;
        
        #endregion

        #region Properties
        /// <summary>
        /// Gets or Sets connection options on serial communication
        /// </summary>
        public SerialOptions ConnectionOptions 
        {
            get { return _serialConn.Options; }
            set { _serialConn.Options = value; }
        }

        /// <summary>
        /// Gets or Sets time between simulation ticks
        /// </summary>
        public double SimulationTimerTickMs 
        {
            get { return _timer.Interval; }
            set { _timer.Interval = value; } 
        }
        #endregion 

        #region Ctor
        /// <summary>
        /// Constructor
        /// </summary>
        public LogicFacade()
        {
            _serialConn = new SerialConnection();

            _rand = new Random();

            _timer = new System.Timers.Timer(5);
            _timer.Enabled = false;
            _timer.Start();

            _logger = new LogStream();
        }

        #region Event Handlers
        /// <summary>
        /// New data recv. Checks the checksum, and if data is OK, it fires a new 
        /// OnInputDataStreamChanged event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _serialConn_DataAvailable(object sender, EventArgs e)
        {
            string str = "";

            str = _serialConn.ReadLine();

            if (checkChecksum(str))
            {
                string[] tmp = str.Split('$');

                AddValuesToLogger(tmp[0]);

                if (OnInputDataStreamChanged != null)
                    OnInputDataStreamChanged(tmp[0]);

            }
            else
            {
                _checksum_errors++;

                if (OnChecksumErrorChanged != null)
                    OnChecksumErrorChanged(_checksum_errors);
            }

            //_serialConn.Flush;
        }
        #endregion

        #endregion

        bool _threadRun = true;

        public void ReadData(object o)
        {
                string str = "";

                while (_threadRun)
                {
                    str = _serialConn.ReadLine();

                    if (checkChecksum(str))
                    {
                        string[] tmp = str.Split('$');

                        AddValuesToLogger(tmp[0]);

                        if (OnInputDataStreamChanged != null)
                            OnInputDataStreamChanged(tmp[0]);

                    }
                    else
                    {
                        _checksum_errors++;

                        if (OnChecksumErrorChanged != null)
                            OnChecksumErrorChanged(_checksum_errors);
                    }

                    Thread.Sleep(0);
                }
                NewInfoMessage("Serial reading stopped!");        
        }


        #region Methods
        /// <summary>
        /// Opens the serial port, and starts to listen for incoming data
        /// </summary>
        /// <returns>true if connection is opened</returns>
        public bool StartSerialStream()
        {
            try
            {
                _checksum_errors = 0;

                //_serialConn.Options = new SerialOptions(_portName, _baudRate, CommStudio.Connections.Parity.None, _dataBits, CommStopBits.One, false, false, true, false, true, true);
                NewInfoMessage("Serial Communication Startet on " + _portName);
                //_serialConn.DataAvailable += new Connection.DataAvailableEventHandler(_serialConn_DataAvailable);
                _serialConn.Open();
        
                NewInfoMessage("Serial Communikation Running");

                _threadRun = true;
                ThreadPool.QueueUserWorkItem(new WaitCallback(ReadData));


            }
            catch (Exception ex)
            {

                NewErrorMessage("Error opening Serial Port: " + ex.Message);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Closes the serial connection
        /// </summary>
        /// <returns>true if connection is closed</returns>
        public bool StopSerialStream()
        {
            if (_serialConn.IsOpen)
            {
                try
                {
                    _threadRun = false;
                    Thread.Sleep(500);
                    _serialConn.Close();
                    NewInfoMessage("Serial Communication Closed");
                    return true;
                }
                catch (Exception ex)
                {
                    NewErrorMessage("Error closing Serial Port: " + ex.Message);
                }
            }else
                NewInfoMessage("Serial Communication Already Closed");

            return false;
        }


        public void CloseSerial()
        {
            if(_threadRun)
                StopSerialStream();
        }

        /// <summary>
        /// Starts data simulation
        /// </summary>
        /// <returns>true if started</returns>
        public bool StartDataSimulation()
        {
            _timer.Enabled = true;
            
            NewInfoMessage("Data Simulation Started");
            _timer.Elapsed += (s, e) =>
            {

                string simData = "" +
                    (500 - _rand.Next(1000)).ToString() + "," +
                    (500 - _rand.Next(1000)).ToString() + "," +
                    (500 - _rand.Next(1000)).ToString() + ",";
                    
                AddValuesToLogger(simData);

                if (OnInputDataStreamChanged != null)
                    OnInputDataStreamChanged(simData);

            };

            return _timer.Enabled;
        }

        /// <summary>
        /// Stops data simulation
        /// </summary>
        /// <returns>true if stopped</returns>
        public bool StopDataSimulation()
        {
            _timer.Enabled = false;
            NewInfoMessage("Data Simulation Stopped");
            return _timer.Enabled;
        }

        public void LoggerSettings(
            string channel0,
            string channel1,
            string channel2)
        {
            _logger.Channel_0_name = channel0;
            _logger.Channel_1_name = channel1;
            _logger.Channel_2_name = channel2;
        }

        public LogStreamConfiguration LogConfiguration 
        {
            get { return _logger.Configuration; }
            set { _logger.Configuration = value; } 
        }


        public bool StartLogger(string filename)
        {
            return _logger.openConnection(filename);
        }

        public bool StopLogger()
        {
            return _logger.closeConnection();;
        }

        public void AddValuesToLogger(string values)
        {
            if(_logger.IsLogging)
                _logger.AddValues(values);
        }

        #endregion

        #region private helpers
        /// <summary>
        /// Checks the checksum, to validate recv data
        /// </summary>
        /// <param name="msg">data to check</param>
        /// <returns>true if data passed the check</returns>
        private bool checkChecksum(string msg)
        {
            string[] values = msg.Split('$');
            
            int calcChecksum = 0;
            int realChecksum = -1;

            if (values.Length == 2)
            {
                calcChecksum = CalcChecksum(values[0]);
               
                try
                {
                    realChecksum = int.Parse(values[1].Trim());
                }
                catch { }

            }

            return calcChecksum == realChecksum;

        }

        /// <summary>
        /// Calculates checksum
        /// </summary>
        /// <param name="msg">msg to be calculated on</param>
        /// <returns>checksum</returns>
        private int CalcChecksum(string msg)
        {
            int calcChecksum = 0;

            foreach (char item in msg.ToCharArray())
            {
                calcChecksum += (byte)item;
            }

            return calcChecksum;

        }

        /// <summary>
        /// Fires new OnInfoMessage event if any listeners
        /// </summary>
        /// <param name="msg">message</param>
        private void NewInfoMessage(string msg)
        {
            if (OnInfoMessage != null)
                OnInfoMessage(msg);
        }

        /// <summary>
        /// Fires new OnErrorMessage event if any listeners
        /// </summary>
        /// <param name="msg">error message</param>
        private void NewErrorMessage(string msg)
        {
            if (OnErrorMessage != null)
                OnErrorMessage(msg);
        }
        #endregion

    }
}
